Skip to content

fix(desktop): install Windows updates without the installer UI - #225

Merged
elkaix merged 1 commit into
mainfrom
fix/desktop-silent-windows-update
Aug 27, 2026
Merged

fix(desktop): install Windows updates without the installer UI#225
elkaix merged 1 commit into
mainfrom
fix/desktop-silent-windows-update

Conversation

@elkaix

@elkaix elkaix commented Aug 27, 2026

Copy link
Copy Markdown
Member

Related Issue

No linked issue — reported directly: on Windows, Restart to update opened the installer wizard and asked the user to click through Next / Next / Install.

Problem

installDownloadedUpdateNow() called autoUpdater.quitAndInstall() with no arguments. In electron-updater 6.8.9 that means isSilent = false, so NsisUpdater.doInstall spawns the setup with only --updated — the assisted NSIS installer UI — and relaunch falls to autoRunAppAfterInstall instead of isForceRunAfter.

A silent installer also reports nothing back, so an install that does not take effect would be invisible. The existing startup receipt proved success only.

What changed

  • Silent install. quitAndInstall(true, true)Pythinker-x.y.z-x64-Setup.exe --updated /S --force-run. No window, and the app relaunches itself. Behaviourally unchanged on macOS: MacUpdater.quitAndInstall() takes no such flags.
  • Failure receipt. reconcileStartupReceipt now returns { settings, failedInstallVersion }. A pending install version that does not match the running version opens the app in status: 'error' with a message naming both versions; the next check clears it. Success detection is unchanged.
  • nsis.allowElevation: false. Soft hardening: it stops a normally launched installer from offering the all-users path, whose per-machine installs are the only ones that still hit UAC_RunElevated on update. It is not a guarantee — an installer started as Administrator still offers both modes. A hard per-user policy would need a customInstallMode macro in build/installer.nsh, which is a larger decision and not part of this change.

Verified against the pinned app-builder-lib 26.15.3 NSIS templates rather than documentation: the assisted installer honours /S and only then applies --force-run (installSection.nsh), the directory the user chose is read back from the HKCU InstallLocation so no /D= is needed (multiUser.nsh), and elevation under /S is gated on an existing per-machine installation (installer.nsi).

Not changed: autoDownload stays false and autoInstallOnAppQuit stays false. Downloading and installing remain two deliberate user actions, and closing the app is still not consent to install.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue (external PRs: the issue must have a maintainer's /approve).
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

Tests

updater.spec.ts asserted only toHaveBeenCalledOnce(), which passes with or without the flags. Added toHaveBeenCalledWith(true, true) — which also makes the eventual electron-updater v7 migration to { isSilent, isForceRunAfter } explicit — plus two failure-receipt tests, and flipped the two existing allowElevation expectations. Reverting all three product values fails exactly 4 tests; 172 pass as shipped.

Still to do

A Windows VM pass: install the current release into a non-default directory, then update. The invariant is that the app stays at the previously selected InstallLocation with no second installation under the default per-user Programs directory, and relaunches on its own after the installer finishes.

`installDownloadedUpdateNow()` called `quitAndInstall()` with no arguments,
so `isSilent` defaulted to false and Restart to update launched the assisted
NSIS installer UI instead of applying the downloaded update. Passing silent
and force-run spawns the setup as `--updated /S --force-run`: no window, and
the app relaunches itself.

The NSIS templates make this safe for a per-user install: the assisted
installer honours `/S`, reads the chosen directory back from the HKCU
InstallLocation, and only elevates when a per-machine installation exists.
`allowElevation: false` stops a normally launched installer from offering
that per-machine path — it is soft hardening, not a guarantee, since an
installer started as Administrator still offers both modes.

A silent installer reports nothing back, so the startup receipt now also
proves failure: a pending install version that does not match the running
version opens the app in an error state naming both versions.
@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 2 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 34dd7f2c-44ed-4bdf-8a78-2b0d3ae761c4

📥 Commits

Reviewing files that changed from the base of the PR and between ad27021 and f556484.

📒 Files selected for processing (5)
  • .changeset/desktop-silent-windows-update.md
  • apps/desktop/package.json
  • apps/desktop/src/updater.ts
  • apps/desktop/tests/packaging-config.spec.ts
  • apps/desktop/tests/updater.spec.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Aug 27, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@pymodel/pythinker-code@f556484
npx https://pkg.pr.new/@pymodel/pythinker-code@f556484

commit: f556484

@elkaix
elkaix merged commit f27686a into main Aug 27, 2026
25 checks passed
@elkaix
elkaix deleted the fix/desktop-silent-windows-update branch August 27, 2026 19:27
elkaix pushed a commit that referenced this pull request Aug 27, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @pymodel/pythinker-code@1.5.0

### Minor Changes

- [#222](#222)
[`6fb46b0`](6fb46b0)
Thanks [@elkaix](https://github.com/elkaix)! - Add Remote Control, which
makes the local web UI reachable from a phone or another computer. Run
`pythinker rc`, or use `/rc` in the terminal UI, and scan the printed QR
code. Enable it with `PYTHINKER_CODE_EXPERIMENTAL_REMOTE_CONTROL=1`.

- [#221](#221)
[`e6778dc`](e6778dc)
Thanks [@elkaix](https://github.com/elkaix)! - Add a task detach action
to the server API. Call `POST
/api/v1/sessions/{session_id}/tasks/{task_id}:detach` to move a running
foreground task to the background.

### Patch Changes

- [#221](#221)
[`e6778dc`](e6778dc)
Thanks [@elkaix](https://github.com/elkaix)! - Report a denied OpenAI
Codex sign-in as cancelled instead of asking for the redirect URL.

- [#223](#223)
[`ad27021`](ad27021)
Thanks [@elkaix](https://github.com/elkaix)! - Remote Control now
authenticates to the relay with its own key instead of the local server
token. Pass `--relay-key` or set
`PYTHINKER_CODE_REMOTE_CONTROL_RELAY_KEY`.

- [#221](#221)
[`e6778dc`](e6778dc)
Thanks [@elkaix](https://github.com/elkaix)! - Show the prompt that
started a subagent turn in the transcript.

- [#219](#219)
[`882835e`](882835e)
Thanks [@elkaix](https://github.com/elkaix)! - Fix sessions that fail to
resume when their session journal is truncated or corrupted, for example
after the disk fills up.

- [#221](#221)
[`e6778dc`](e6778dc)
Thanks [@elkaix](https://github.com/elkaix)! - Retry a failed session
journal repair before writing new records, so no message is appended
behind a corrupted tail.
## @pymodel/pythinker-desktop@0.3.8

### Patch Changes

- [#225](#225)
[`f27686a`](f27686a)
Thanks [@elkaix](https://github.com/elkaix)! - Install Windows updates
in the background instead of opening the installer wizard, and report an
update that did not take effect.
## pythinker@0.9.7

### Patch Changes

- [#221](#221)
[`e6778dc`](e6778dc)
Thanks [@elkaix](https://github.com/elkaix)! - Fix duplicated streaming
output when a session is opened twice at the same time.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Added Remote Control for accessing the local interface from other
devices.
- Added the ability to detach running tasks and continue them in the
background.
- Added dedicated authentication support for Remote Control connections.

- **Bug Fixes**
- Improved sign-in cancellation reporting and subagent prompt
visibility.
  - Improved recovery and repair of corrupted session history.
- Prevented duplicate streaming output when sessions are opened
concurrently.
- Windows updates now install silently in the background and report
unsuccessful updates.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
elkaix added a commit that referenced this pull request Aug 27, 2026
## Related Issue

No linked issue — reported from the app: the v0.3.8 release-notes
popover read "Pythinker Desktop 0.3.8 (stable channel), built from
PyModel/pythinker-code@`<tt>`0f49851`</tt>`.", with the tags printed
literally.

## Problem

Two separate causes produced that one popover.

1. **The body was a build stamp.** `desktop-release.yml` created the
draft with a fixed `--notes "Pythinker Desktop <v> (<channel> channel),
built from <commit>."`. Nothing else ever wrote the body, so the updater
had nothing to say about a version.
2. **The notes arrive as HTML.** electron-updater's GitHub provider
reads the releases Atom feed, whose `<content type="html">` is the body
GitHub has already rendered. The renderer treats `releaseNotes` as
Markdown, so the tags GitHub emits — `<p>`, `<a>`, `<tt>` — printed as
text.

Fixing only the body would have left the markup visible, since a real
changelog renders to `<ul>`/`<li>`/`<a>` too.

## What changed

- **`apps/desktop/scripts/desktop-release.mjs`** — new exported
`desktopReleaseNotes()` and a `notes` subcommand. It takes the `##
<version>` section of `apps/desktop/CHANGELOG.md`, strips the changesets
prefix (`[#225](…) [`sha`](…) Thanks [@user](…)! - `), and returns the
bullets plus a `Built from <commit>` footer.
- **`.github/workflows/desktop-release.yml`** — the prepare job writes
those notes to a file and passes `--notes-file`. A **stable** release
whose version has no changelog entry now fails here instead of
publishing; preview channels fall back to a one-line description, since
a nightly version never appears in the changelog. The source-commit URL
stays in the body because the draft-resume check on line 120 gates on
`.body | contains($source_url)`.
- **`apps/desktop/src/updater.ts`** — `releaseNotesText` reduces HTML
notes to text (list items to `- `, block ends to newlines, entities
decoded), and leaves notes without markup untouched.
- **`AGENTS.md`** — records that changeset text is shipped text: it
becomes the release body users read, and a body must never be a build
stamp.

## Checklist

- [x] I have read the
[CONTRIBUTING](https://github.com/PyModel/pythinker-code/blob/main/CONTRIBUTING.md)
document.
- [ ] I have linked a related issue (external PRs: the issue must have a
maintainer's `/approve`).
- [x] I have added tests that prove my feature works.
- [x] Ran `gen-changesets` skill, or this PR needs no changeset.
- [x] Ran `gen-docs` skill, or this PR needs no doc update.

### Tests

`scripts/release/desktop-release.test.mjs` covers extraction, the
next-heading boundary, the stable-release gate, the preview fallback,
and the required source URL. `updater.spec.ts` asserts the exact HTML
from the live v0.3.8 feed comes out as text, and that plain notes are
unchanged. `desktop-release-workflow.spec.ts` pins `--notes-file` and
forbids the old literal. Reverting both product changes fails exactly 2
tests; 175 + 25 pass as shipped.

### What users will see

Instead of the build stamp, the v0.3.9 popover will read: `- Show the
changelog for the new version in the update dialog instead of a build
stamp with raw HTML tags.`


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Update dialogs now display readable changelog content for new
versions.
* Release notes preserve lists and links as clean, formatted text
without raw HTML or script content.
* Desktop releases now publish version-specific changelog entries
instead of build information.

* **Bug Fixes**
  * Improved handling of plain-text and HTML-formatted release notes.
* Prevented empty or malformed release notes from appearing in update
prompts.

* **Reliability**
* Stable releases without valid changelog entries are blocked from
publication.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant